package com.jungle.bfi.metadata.swagger;

import com.jungle.bfi.metadata.swagger.enums.PathType;
import com.jungle.bfi.metadata.swagger.vo.ApiPath;
import com.jungle.bfi.metadata.swagger.vo.SchemaInfo;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.tags.Tag;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

@Slf4j
public class SwaggerReader {

    public void read(String apiUrl, Consumer<ApiPath> pathConsumer, Consumer<List<SchemaInfo>> schemaConsumer) {
        SwaggerParseResult result = getSwaggerParseResult(apiUrl);

        // 获取解析后的OpenAPI对象
        OpenAPI openAPI = result.getOpenAPI();
        if (openAPI == null) {
            log.info("请求地址({})解析失败", apiUrl);
            return;
        }
        // 1、获取顶级系统信息
        readSystemInfo(openAPI, pathConsumer);

        // 2、获取接口标签
        readTags(openAPI, pathConsumer);

        // 3、获取路径列表(接口列表)
        readPaths(openAPI, pathConsumer);

        // 4、读取组件,并保存组件
        readComponents(openAPI, schemaConsumer);
    }

    private static SwaggerParseResult getSwaggerParseResult(String apiUrl) {
        ParseOptions options = new ParseOptions();
        options.setResolve(true); // 设置为true以解析所有外部$ref引用

        OpenAPIParser parser = new OpenAPIParser();

        // 解析Swagger文件
        return parser.readLocation(apiUrl, null, options);
    }

    private void readSystemInfo(OpenAPI openAPI, Consumer<ApiPath> consumer) {
        Info info = openAPI.getInfo();

        ApiPath apiPath = new ApiPath();
        apiPath.setType(PathType.SYSTEM);
        apiPath.setSummary(info.getTitle());
        apiPath.setDescription(info.getDescription());

        //String summary = info.getSummary();
        //String termsOfService = info.getTermsOfService();

        consumer.accept(apiPath);
    }

    private void readTags(OpenAPI openAPI, Consumer<ApiPath> consumer) {
        // 1、接口标签
        List<Tag> tags = openAPI.getTags();
        if (tags == null || tags.isEmpty()) {
            log.warn("接口标签为空");
            return;
        }
        tags.forEach(tag -> {
            ApiPath apiPath = new ApiPath();
            apiPath.setType(PathType.MODULE);
            apiPath.setSummary(tag.getName());
            apiPath.setDescription(tag.getDescription());

            consumer.accept(apiPath);
        });
    }

    private void readPaths(OpenAPI openAPI, Consumer<ApiPath> consumer) {
        // 1、接口路径
        for (Map.Entry<String, PathItem> entry : openAPI.getPaths().entrySet()) {
            String path = entry.getKey();
            PathItem pathItem = entry.getValue();
            pathItem.readOperationsMap().forEach((method, operation) -> {
                // 2、接口名称
                String summary = operation.getSummary();
                // 3、接口标签
                List<String> tags = operation.getTags();
                // 4、接口描述
                String description = operation.getDescription();
                // 获取接口响应 只获取响应码为200的
                ApiResponse apiResponse = operation.getResponses().get("200");
                Content content = apiResponse.getContent();
                if (content == null) {
                    log.warn("接口响应为空");
                    return;
                }
                content.forEach((contentType, mediaType) -> {
                    // 5、接口响应类型

                    ApiPath apiPath = new ApiPath();
                    apiPath.setType(PathType.FUNCTION);
                    apiPath.setSummary(summary);
                    apiPath.setDescription(description);

                    apiPath.setPath(path);
                    apiPath.setMethod(method.name());
                    apiPath.setTags(tags);
                    apiPath.setContentType(contentType);
                    // 6、接口响应对象引用

                    Schema<?> schema = mediaType.getSchema();

                    if (schema instanceof ArraySchema || schema.getItems() != null) {
                        apiPath.setSchemaRef(toSchemaRef(schema.getItems().get$ref()));
                        apiPath.setSchemaType("array");
                    } else if (schema.get$ref() != null) {
                        apiPath.setSchemaRef(toSchemaRef(schema.get$ref()));
                        apiPath.setSchemaType("object");
                    } else {
                        apiPath.setSchemaType(schema.getType());
                    }
                    consumer.accept(apiPath);
                });
            });
        }
    }

    private String toSchemaRef(String schemaRef) {
        return schemaRef.substring(schemaRef.lastIndexOf("/") + 1);
    }


    private void readComponents(OpenAPI openAPI, Consumer<List<SchemaInfo>> consumer) {
        Components components = openAPI.getComponents();
        String[] paths = new String[]{"$"};
        components.getSchemas().forEach((schemaName, schema) -> {
            List<SchemaInfo> list = readSchema(schemaName, paths, schema);
            // 保存数据
            log.info("{} : {}", schemaName, list.size());
            consumer.accept(list);
        });
    }


    private List<SchemaInfo> readSchema(String schemaName, String[] parentId, Schema<?> schema) {
        Map<Class<?>, Function<Schema<?>, List<SchemaInfo>>> schemaHandlers = new HashMap<>();

        schemaHandlers.put(ObjectSchema.class, s -> readObjectSchema(schemaName, parentId, (ObjectSchema) s));
        schemaHandlers.put(ArraySchema.class, s -> readArraySchema(schemaName, parentId, (ArraySchema) s));

        schemaHandlers.put(StringSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "string"));
        schemaHandlers.put(IntegerSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "integer"));
        schemaHandlers.put(NumberSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "number"));
        schemaHandlers.put(BooleanSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "boolean"));
        schemaHandlers.put(DateSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "date"));
        schemaHandlers.put(DateTimeSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "date-time"));
        schemaHandlers.put(EmailSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "email"));
        schemaHandlers.put(PasswordSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "password"));
        schemaHandlers.put(UUIDSchema.class, s -> readPrimitiveSchema(schemaName, parentId, s, "uuid"));

        schemaHandlers.put(ComposedSchema.class, s -> readComposedSchema((ComposedSchema) s));
        schemaHandlers.put(MapSchema.class, s -> readMapSchema((MapSchema) s));
        schemaHandlers.put(JsonSchema.class, s -> readJsonSchema((JsonSchema) s));

        if (schemaHandlers.containsKey(schema.getClass())) {
            return schemaHandlers.get(schema.getClass()).apply(schema);
        } else if (StringUtils.isNotBlank(schema.get$ref())) {
            return readRefSchema(schemaName, parentId, schema);
        } else {
            log.error("unknown schema type: {}, schemaName: {}, parentId: {}", schema.getClass().getName(), schemaName, String.join(", ", parentId));
            return Collections.emptyList();
        }
    }


    private List<SchemaInfo> readObjectSchema(String schemaName, String[] paths, ObjectSchema schema) {
        SchemaInfo schemaInfo = new SchemaInfo();
        schemaInfo.setPaths(paths);
        schemaInfo.setName(schemaName);
        schemaInfo.setOriginType("object");
        schemaInfo.setTitle(schema.getTitle());
        schemaInfo.setType(schema.getType());

        int length = paths.length;
        String[] thisPaths = new String[length + 1];
        System.arraycopy(paths, 0, thisPaths, 0, length);
        thisPaths[length] = schemaName;

        List<SchemaInfo> list = new ArrayList<>();
        list.add(schemaInfo);
        Map<String, Schema> properties = schema.getProperties();
        if(properties == null){
            log.info("{} has no properties", schemaName);
        }else {
            properties.forEach((attrName, attrSchema) -> list.addAll(readSchema(attrName, thisPaths, attrSchema)));
        }
        return list;
    }

    private List<SchemaInfo> readArraySchema(String schemaName, String[] paths, ArraySchema schema) {
        SchemaInfo schemaInfo = new SchemaInfo();
        schemaInfo.setPaths(paths);
        schemaInfo.setName(schemaName);
        schemaInfo.setOriginType("array");
        schemaInfo.setTitle(schema.getTitle());
        schemaInfo.setType(schema.getType());

        int length = paths.length;
        String[] thisPaths = new String[length + 1];
        System.arraycopy(paths, 0, thisPaths, 0, length);
        thisPaths[length] = schemaName;

        List<SchemaInfo> list = new ArrayList<>();
        list.add(schemaInfo);
        List<SchemaInfo> nodes = readSchema(schemaName, thisPaths, schema.getItems());
        list.addAll(nodes);
        return list;
    }

    private List<SchemaInfo> readPrimitiveSchema(String schemaName, String[] paths, Schema<?> schema, String originType) {
        SchemaInfo schemaInfo = new SchemaInfo();
        schemaInfo.setName(schemaName);
        schemaInfo.setPaths(paths);
        schemaInfo.setOriginType(originType);
        schemaInfo.setType(schema.getType());
        schemaInfo.setFormat(schema.getFormat());
        schemaInfo.setDescription(schema.getDescription());
        schemaInfo.setMaximum(schema.getMaximum());
        schemaInfo.setMinimum(schema.getMinimum());
        return Collections.singletonList(schemaInfo);
    }

    private List<SchemaInfo> readRefSchema(String schemaName, String[] paths, Schema<?> schema) {
        SchemaInfo schemaInfo = new SchemaInfo();
        schemaInfo.setName(schemaName);
        schemaInfo.setPaths(paths);
        schemaInfo.setOriginType("ref");
        schemaInfo.setRef(toSchemaRef(schema.get$ref()));
        schemaInfo.setType(schema.getType());
        schemaInfo.setFormat(schema.getFormat());
        schemaInfo.setDescription(schema.getDescription());
        schemaInfo.setMaximum(schema.getMaximum());
        schemaInfo.setMinimum(schema.getMinimum());
        log.info("This is a RefSchema : {}" , schema.get$ref());

        return Collections.singletonList(schemaInfo);
    }

    private List<SchemaInfo> readComposedSchema(ComposedSchema schema) {
        log.info("The ComposedSchema is not support");
        return Collections.emptyList();
    }

    private List<SchemaInfo> readJsonSchema(JsonSchema schema) {
        log.info("The JsonSchema is not support");
        return Collections.emptyList();
    }

    private List<SchemaInfo> readMapSchema(MapSchema schema) {
        log.info("The MapSchema is not support");
        return Collections.emptyList();
    }

}
